﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data;
using System.Data.Entity;
using System.Web.Security;
using System.Net.Mail;
using System.Net;

namespace cs499.Database
{
    public static class DepartmentExtension
    {

        public static Course CreateCourse(this Department department, string coursetitle, int number, int numberofsections, int maxenroll, int[] RequiredPlaceIDs = null)
        {
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return null;

            Course newCourse = db.Set<Course>().Where(coursetofind => coursetofind.DepartmentId == department.Id).FirstOrDefault(coursetofind => coursetofind.Number == number);
            if (newCourse == null)
            {
                newCourse = new Course();
                newCourse.Department = thisdepincontext;
                newCourse.CourseTitle = coursetitle;

                newCourse.DepartmentId = thisdepincontext.Id;
                newCourse.Number = number;
                newCourse.NumberOfSections = numberofsections;
                newCourse.MaximumEnrollment = maxenroll;
                if (RequiredPlaceIDs != null)
                {
                    List<Place> plcreq = db.Places.Include("DependentCourses").Where(placetofind => RequiredPlaceIDs.Contains(placetofind.Id)).ToList();
                    foreach (Place plc in plcreq)
                    {
                        plc.DependentCourses.Add(newCourse);
                    }
                    newCourse.PlaceRequirements = plcreq;
                    newCourse.PlaceRequirementsstring = newCourse.PlaceRequirements.PlaceListToString();
                }
                db.Set<Course>().Add(newCourse);
                db.SaveChanges();
                //db.Entry(newCourse)
                return newCourse;
            }
            else return null;
        }

        public static void SaveCourse(this Department department, int courseid, string coursetitle, int number, int numberofsections, int maxenroll, int[] RequiredPlaceIDs = null)
        {
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);

            Course course = db.Set<Course>().Include("PlaceRequirements").FirstOrDefault(coursetofind => coursetofind.Id == courseid);
            if (course != null)
            {
                course.CourseTitle = coursetitle;
                course.Number = number;
                course.NumberOfSections = numberofsections;
                course.MaximumEnrollment = maxenroll;
                if (RequiredPlaceIDs != null)
                {
                    List<Place> plcreq = db.Places.Include("DependentCourses").Where(placetofind => RequiredPlaceIDs.Contains(placetofind.Id)).ToList();
                    
                    foreach (Place plc in plcreq)
                    {
                        plc.DependentCourses.Add(course);
                    }
                    course.PlaceRequirements.Clear();
                    course.PlaceRequirements = plcreq;
                    course.PlaceRequirementsstring = course.PlaceRequirements.PlaceListToString();
                }
                db.SaveChanges();
            }
            else 
                throw new ApplicationException("The course you were trying to edit does not exist.");
        }

        public static Course GetCourse(this Department department, string coursetitle = "", int number = -1)
        {
            if (coursetitle == "" && number == -1) return null;
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return null;

            Course newCourse = db.Set<Course>().Where(coursetofind => coursetofind.DepartmentId == department.Id).FirstOrDefault(coursetofind => coursetofind.CourseTitle == coursetitle || coursetofind.Number == number);
            return newCourse;
        }

        public static bool DeleteCourse(this Department department, string coursetitle = "", int number = -1, int ID = -1)
        {
            if (coursetitle == "" && number == -1 && ID == -1) return false;
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return false;

            Course course = db.Set<Course>().Where(coursetofind => coursetofind.DepartmentId == department.Id).FirstOrDefault(coursetofind => coursetofind.CourseTitle == coursetitle || coursetofind.Number == number || coursetofind.Id == ID);
            return course.Delete();
        }

        public static Building CreateBuilding(this Department department, string buildingname, string buildingcode)
        {
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return null;

            Building newBuilding = db.Set<Place>().OfType<Building>().Where(placetofind => placetofind.Department.Id == department.Id).FirstOrDefault(placetofind => placetofind.Code == buildingcode || placetofind.Name == buildingname);
            if (newBuilding == null)
            {
                newBuilding = new Building();
                newBuilding.Department = thisdepincontext;
                newBuilding.PlaceName = buildingcode;
                newBuilding.Name = buildingname;
                newBuilding.Code = buildingcode;

                db.Set<Place>().Add(newBuilding);
                db.SaveChanges();
                return newBuilding;
            }
            else return null;
        }

        public static Place GetPlace(this Department department, string PlaceName = "", int ID = -1)
        {
            if (PlaceName == "" && ID == -1) return null;
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return null;
            Place place = db.Set<Place>().Where(placetofind => placetofind.Department.Id == department.Id).FirstOrDefault(placetofind => placetofind.PlaceName == PlaceName || placetofind.Id == ID);
            return place;
        }

        public static bool DeletePlace(this Department department, string PlaceName = "", int ID = -1)
        {
            if (PlaceName == "" && ID == -1) return false;
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return false;


            Place place = db.Set<Place>().Include("DependentCourses").Where(placetofind => placetofind.Department.Id == department.Id).FirstOrDefault(placetofind => placetofind.PlaceName == PlaceName || placetofind.Id == ID);
            return place.Delete();
        }

        public static Building GetBuilding(this Department department, string buildingname = "", string buildingcode = "", int ID = -1, string strID = "")
        {
            if (buildingname == "" && buildingcode == "" && ID == -1 && strID == "") return null;
            int idnum;
            if (ID == -1 && strID.Length > 0 && int.TryParse(strID, out idnum)) ID = idnum;

            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return null;

            Building newBuilding = db.Set<Place>().OfType<Building>().Where(placetofind => placetofind.Department.Id == department.Id).FirstOrDefault(placetofind => placetofind.Id == ID || placetofind.Code == buildingcode || placetofind.Name == buildingname);
            return newBuilding;
        }

        public static Faculty CreateFaculty(this Department department, Guid aspuserproviderkey, string emailaddress = "", bool isEmail = false)
        {
            if (aspuserproviderkey.Equals(Guid.Empty))
            {
                MembershipUser newuser=null;
                string generatedPassword;
                try
                {
                    generatedPassword = Membership.GeneratePassword(8, 1);
                    newuser = Membership.CreateUser(emailaddress, generatedPassword, emailaddress);
                    if (newuser == null) return null;
                }
                catch (Exception)
                {
                    return null;//orphan records in asp database
                }
                if (newuser == null) return null;
                else
                {
                    Roles.AddUserToRole(newuser.UserName, "faculty");
                    aspuserproviderkey = (Guid)newuser.ProviderUserKey;
                    string mailBody = "A ClassGrab account has been created on your behalf.\n\n" +
                    
                            "Your username is your email address.\n" +
                            "Your password is: " + generatedPassword + "\n\n" +
                            "Navigate to the Preferences tab to change your password.";

                    if (isEmail)
                    {
                        try
                        {
                            var client = new SmtpClient("smtp.gmail.com", 587)
                            {
                                Credentials = new NetworkCredential("classgrab@gmail.com", "classgrab!00"),
                                EnableSsl = true
                            };
                            client.Send("classgrab@gmail.com", newuser.UserName, "ClassGrab Account Created", mailBody);
                        }
                        catch (Exception /*ex*/) { }
                    }
                    
                }
            }
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return null;

            Faculty newFaculty = db.Set<Faculty>().Where(Facultytofind => Facultytofind.Department.Id == department.Id).FirstOrDefault(Facultytofind => Facultytofind.aspnet_userID == aspuserproviderkey);
            if (newFaculty == null)
            {
                newFaculty = new Faculty();
                newFaculty.DepartmentID = thisdepincontext.Id;
                newFaculty.aspnet_userID = aspuserproviderkey;

                db.Set<Faculty>().Add(newFaculty);
                db.SaveChanges();
                return newFaculty;
            }
            else return null;
        }

        public static Faculty GetFaculty(this Department department, Guid aspuserproviderkey, string emailaddress = "")
        {
            if (emailaddress == "" && aspuserproviderkey == Guid.Empty) return null;
            else if (emailaddress != "" && aspuserproviderkey == Guid.Empty)
            {
                MembershipUser tempuser = Membership.GetUser(emailaddress);
                if (tempuser == null) return null;
                else aspuserproviderkey = (Guid)tempuser.ProviderUserKey;
            }
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return null;

            Faculty newFaculty = db.Set<Faculty>().Where(Facultytofind => Facultytofind.Department.Id == department.Id).FirstOrDefault(Facultytofind => Facultytofind.aspnet_userID == aspuserproviderkey);
            return newFaculty;
        }

        public static bool DeleteFaculty(this Department department, Guid aspuserproviderkey, string emailaddress = "")
        {
            if (emailaddress == "" && aspuserproviderkey == Guid.Empty) return false;
            else if (emailaddress != "" && aspuserproviderkey == Guid.Empty)
            {
                MembershipUser tempuser = Membership.GetUser(emailaddress);
                if (tempuser == null) return false;
                else aspuserproviderkey = (Guid)tempuser.ProviderUserKey;
            }
            Model1Container db = new Model1Container();
            Department thisdepincontext = db.Set<Department>().FirstOrDefault(deptofind => deptofind.Id == department.Id && deptofind.Name == department.Name);
            if (thisdepincontext == null) return false;

            Faculty newFaculty = db.Set<Faculty>().Where(Facultytofind => Facultytofind.Department.Id == department.Id).FirstOrDefault(Facultytofind => Facultytofind.aspnet_userID == aspuserproviderkey);
            return newFaculty.Delete();
        }

        public class DbIterator<T>
        {
            public DbIterator(List<T> data)
            {
                _data = data.ToArray<T>();
            }

            public T[] toArray()
            {
                return _data;
            }

            public T next()
            {
                if (_index >= _data.Length - 1)
                {
                    _index = 0;
                }
                else
                {
                    _index = _index + 1;
                }

                return _data[_index];
            }

            private T[] _data = null;
            private int _index = -1;

        }

        public static Boolean courseCanBeInRoom(CourseInstance course, Room room)
        {
            Boolean result = false;

            if (course.Course.PlaceRequirements.Count != 0)
            {
                foreach (Place requirement in course.Course.PlaceRequirements)
                {
                    if (result == false)
                    {
                        if (requirement is Room)
                        {
                            result = ((Room)requirement).Number == room.Number;
                        }
                        else
                        {
                            foreach (Room deptRoom in ((Building)requirement).Rooms)
                            {
                                if (result == false)
                                {
                                    result = courseCanBeInRoom(course, deptRoom);
                                }
                            }
                        }

                    }
                }
            }
            else
            {
                result = true;
            }

            return result;
        }


        public static Boolean timeSlotsOverLap(TimeSlot a, TimeSlot b)
        {
            return a.OverlapsWith(b);
        }

        public static Boolean facultyIsFreeAtTime(TimeSlot timeSlot, Faculty faculty, CourseInstance[] instances)
        {
            Boolean result = true;

            foreach (CourseInstance facultyInstance in instances)
            {
                if (facultyInstance.TaughtBy != null)
                {
                    if (facultyInstance.TaughtBy.Id == faculty.Id && facultyInstance.TimeSlot != null && result == true)
                    {
                        result = (timeSlotsOverLap(timeSlot, facultyInstance.TimeSlot) == false);
                    }
                }
            }

            return result;
        }

        public static Boolean roomIsFreeAtTime(TimeSlot timeSlot, Room room, CourseInstance[] instances)
        {
            Boolean result = true;

            foreach (CourseInstance instance in instances)
            {
                if (instance.Room != null)
                {
                    if (instance.Room.Id == room.Id && instance.TimeSlot != null && result == true)
                    {
                        result = (timeSlotsOverLap(timeSlot, instance.TimeSlot) == false);
                    }
                }
            }

            return result;
        }

        public static Boolean roomRequirementsAreMet(CourseInstance course, Room room)
        {
            
            Room[] requiredRooms = getRequiredRooms(course);

            foreach (Room requiredRoom in requiredRooms)
            {
                if (requiredRoom.Id == room.Id) return true;
            }

            return false;
        }

        public static Room[] getRequiredRooms(CourseInstance course)
        {
            List<Room> rooms = new List<Room>();

            foreach (Place place in course.Course.PlaceRequirements)
            {
                if (place is Room)
                {
                    rooms.Add((Room)place);
                }
                else
                {
                    foreach (Room room in ((Building)place).Rooms)
                    {
                        rooms.Add(room);
                    }
                }
            }

            return rooms.ToArray();
        }

        public static Boolean entriesForCourseAreValid(CourseInstance course, Room room, TimeSlot timeSlot, Faculty faculty, CourseInstance[] instances)
        {
            Boolean result = false;

            if (roomIsFreeAtTime(timeSlot, room, instances) &&
                facultyIsFreeAtTime(timeSlot, faculty, instances) &&
                roomRequirementsAreMet(course, room))
            {
                result = true;
            }

            return result;
        }


        public static CourseInstance[] generateInstancesFromPreferencesAndCourses(Schedule schedule, Faculty[] faculty, Course[] courses, Model1Container db)
        {
            
            List<CourseInstance> allpreferences = new List<CourseInstance>();
            Dictionary<int,int> sectiondict = new Dictionary<int,int>();
            
            foreach (Faculty facultyMember in faculty)
            {
                foreach (CourseInstance preferred in GetFacultyPreferences(facultyMember.DepartmentID, facultyMember.UserName, db))
                {
                    CourseInstance newinstance = new CourseInstance();
                    newinstance.Room = preferred.Room;
                    newinstance.TaughtBy = preferred.PreferredBy;
                    newinstance.TimeSlot = preferred.TimeSlot;
                    newinstance.Schedule = schedule;
                    newinstance.Course = preferred.Course;
                    if (sectiondict.ContainsKey(preferred.Course.Number))
                    {
                        newinstance.SectionNumber = sectiondict[preferred.Course.Number]++;
                    }
                    else
                    {
                        newinstance.SectionNumber = 1;
                        sectiondict[preferred.Course.Number]=2;
                    }
                    
                    allpreferences.Add(newinstance);
                    //schedule.ScheduledCourses.Add(newinstance);
                    
                }
            }
            List<CourseInstance> instancestoadd=new List<CourseInstance>();
            foreach (Course course in courses)
            {
                int sectionsscheduled = allpreferences.Where(coursetofind => coursetofind.Course.Id == course.Id).ToList().Count;
                int sectionnum = sectionsscheduled;
                int deficitcourses = course.NumberOfSections - sectionsscheduled;
                while (deficitcourses>0)
                {
                    CourseInstance newinstance = new CourseInstance();
                    newinstance.Schedule = schedule;
                    newinstance.Course = course;
                    if (sectiondict.ContainsKey(course.Number))
                    {
                        newinstance.SectionNumber = sectiondict[course.Number]++;
                    }
                    else
                    {
                        newinstance.SectionNumber = 1;
                        sectiondict[course.Number] = 2;
                    }
                    instancestoadd.Add(newinstance);
                    deficitcourses--;
                }
            }
            allpreferences.AddRange(instancestoadd);
            return allpreferences.ToArray();
        }

        public static List<CourseInstance> GetFacultyPreferences(int departmentid, String UserName, Model1Container db)
        {//get prefs for a facultymember -1 for current (blank or asp_username for current)
            List<CourseInstance> retlist = new List<CourseInstance>();
            if (UserName == "" || UserName == null) return retlist;
            
            Database.Faculty currentfaculty;

            int facultytofindid;
            Department CurrentDepartment = ListOfDepartment.GetDepartment(ID: departmentid);
            currentfaculty = CurrentDepartment.GetFaculty(Guid.Empty, emailaddress: UserName);
            if (currentfaculty != null) facultytofindid = currentfaculty.Id;
            else return retlist;
                

            Faculty faculty = db.Set<Faculty>()
                .Include("PreferredInstances")
                .Include("PreferredInstances.Room")
                .Include("PreferredInstances.Course")
                .Include("PreferredInstances.TimeSlot")
                .FirstOrDefault(fac => fac.Id == facultytofindid);
            if (faculty == null) return retlist;

            foreach (CourseInstance ci in faculty.PreferredInstances)
            {
                retlist.Add(ci);
            }

            return retlist;
        }

        public static Schedule GenerateSchedule(this Department department, Model1Container db)
        {
            //Model1Container db = new Model1Container();
            Schedule schedule = new Schedule();
            department = db.Set<Department>().Include("Courses").FirstOrDefault(dep => dep.Id == department.Id);
            schedule.Department = department;
            department.Schedule = schedule;

            DbIterator<Faculty> faculty = new DbIterator<Faculty>(db.Set<Faculty>().Where(factofind => factofind.DepartmentID == department.Id).ToList());
            DbIterator<TimeSlot> timeslots = new DbIterator<TimeSlot>(db.Set<TimeSlot>().ToList());
            DbIterator<Room> rooms = new DbIterator<Room>(db.Set<Room>().Where(roomtofind => roomtofind.DepartmentId == department.Id).ToList());
            DbIterator<Course> courses = new DbIterator<Course>(db.Set<Course>().Include("PlaceRequirements").Where(roomtofind => roomtofind.DepartmentId == department.Id).ToList());

            // TODO: Sort the previous lists

            CourseInstance[] courseInstances = generateInstancesFromPreferencesAndCourses(schedule,faculty.toArray(), department.Courses.ToArray<Course>(), db);


            foreach (CourseInstance instance in courseInstances)
            {
                Boolean conflict = false;
                Room startRoom = rooms.next();
                Room room = startRoom;

                if (faculty.toArray().Length   == 0 ||
                    timeslots.toArray().Length == 0 ||
                    rooms.toArray().Length     == 0)
                {
                    conflict = true;
                }


                while ((instance.Room == null || instance.TaughtBy == null || instance.TimeSlot == null )&& conflict == false)
                {

                    if (roomRequirementsAreMet(instance, room) == true) // Course can be at room
                    {

                        TimeSlot startTimeSlot = timeslots.next();
                        TimeSlot timeSlot = startTimeSlot;
                        while ((instance.TimeSlot == null || instance.TaughtBy == null) && conflict == false)
                        {

                            if (roomIsFreeAtTime(timeSlot, room, courseInstances) == true) // Course can be at timeslot
                            {
                                Faculty startFaculty = faculty.next();
                                Faculty facultyMember = startFaculty;
                                while (instance.TaughtBy == null && conflict == false)
                                {
                                    if (entriesForCourseAreValid(instance, room, timeSlot, facultyMember, courseInstances) == true) // Course can be with Faculty Member
                                    {
                                        instance.Room = room;
                                        instance.TimeSlot = timeSlot;
                                        instance.TaughtBy = facultyMember;
                                    }
                                    else if(faculty.toArray().Length == 1)
                                    {
                                        conflict = true;
                                    }

                                    
                                    facultyMember = faculty.next();
                                    if (facultyMember == startFaculty && faculty.toArray().Length > 1)
                                    {
                                        conflict = true;
                                    }
                                }
                                conflict = false;

                            }
                            else if (timeslots.toArray().Length == 1)
                            {
                                conflict = true;
                            }

                            timeSlot = timeslots.next();
                            if (timeSlot == startTimeSlot && timeslots.toArray().Length > 1)
                            {
                                conflict = true;
                            }

                        }
                        conflict = false;
                    }
                    else if (rooms.toArray().Length == 1)
                    {
                        conflict = true;
                    }

                    room = rooms.next();
                    if (room == startRoom && rooms.toArray().Length > 1)
                    {
                        conflict = true;
                    }

                }

                if(conflict == true)
                {
                    schedule.ConflictedCourses.Add(instance);
                }
                else
                {
                    schedule.ScheduledCourses.Add(instance);
                }
            }

            return schedule;
        }
    }



    public partial class Department
    {
        public static Schedule GetSchedule(int departmentid)
        {
            Model1Container db = new Model1Container();

            Schedule schedule = db.Set<Schedule>()
                .Include("ScheduledCourses")
                .Include("ScheduledCourses.TaughtBy")
                .Include("ScheduledCourses.Course")
                .Include("ScheduledCourses.Room")
                .Include("ScheduledCourses.TimeSlot")
                .Include("ConflictedCourses")
                .Include("ConflictedCourses.TaughtBy")
                .Include("ConflictedCourses.Course")
                .Include("ConflictedCourses.Room")
                .Include("ConflictedCourses.TimeSlot")
                .Include("Department")
                .FirstOrDefault(sch=> sch.Department.Id == departmentid);
            return schedule;
        }
        public static List<CourseInstance> GetScheduledCourses(int departmentid, int placeid, int facultyid)
        {
            Schedule schedule = Department.GetSchedule(departmentid);
            if (schedule == null)
            {
                return new List<CourseInstance>();
            }
            else
            {
                List<CourseInstance> retlist = schedule.ScheduledCourses.ToList();
                if (placeid != -1) retlist = retlist.Where(coursetofilter => coursetofilter.Room.Id == placeid || coursetofilter.Room.BuildingID == placeid).ToList();
                if (facultyid != -1) retlist = retlist.Where(coursetofilter => coursetofilter.TaughtBy.Id == facultyid).ToList();
                return retlist;
            }
        }
        public static List<CourseInstance> GetConflictedCourses(int departmentid)
        {
            Schedule schedule = Department.GetSchedule(departmentid);
            if (schedule == null)
            {
                return new List<CourseInstance>();
            }
            else
            {
                return schedule.ConflictedCourses.ToList();
            }
        }
    }
}